/**

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
 * Created on Mar 30, 2005
 */
package com.bigdata.rdf.axioms;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

import org.openrdf.model.Value;

import com.bigdata.btree.keys.IKeyBuilder;
import com.bigdata.btree.keys.KeyBuilder;
import com.bigdata.io.LongPacker;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.internal.IVUtility;
import com.bigdata.rdf.model.BigdataStatement;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.bigdata.rdf.model.BigdataValueFactoryImpl;
import com.bigdata.rdf.model.StatementEnum;
import com.bigdata.rdf.rio.StatementBuffer;
import com.bigdata.rdf.spo.ISPO;
import com.bigdata.rdf.spo.SPO;
import com.bigdata.rdf.spo.SPOKeyOrder;
import com.bigdata.rdf.store.AbstractTripleStore;

/**
 * A collection of axioms.
 * <p>
 * Axioms are generated by {@link AbstractTripleStore#create()} based on
 * its configured properties. While the implementation class declares axioms in
 * terms of RDF {@link Value}s, the {@link BaseAxioms} only retains the set of
 * {s:p:o} tuples for the term identifiers corresponding those those
 * {@link Value}s. That {s:p:o} tuple array is the serialized state of this
 * class. When an {@link AbstractTripleStore} is reopened, the axioms are
 * de-serialized from a property in the global row store.
 * 
 * @author personickm
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
public abstract class BaseAxioms implements Axioms, Externalizable {
    
//    /**
//     * The axioms in SPO order.
//     */
//    private transient BTree btree;
    
    /**
     * The axioms.
     */
    private Set<SPO> axioms;
    
//    /**
//     * Used to format keys for that {@link BTree}.
//     */
//    private transient SPOTupleSerializer tupleSer;

//    /**
//     * Non-<code>null</code> iff the ctor specifies this value.
//     */
//    private final transient AbstractTripleStore db;
    
    /**
     * The namespace of the associated kb instance. This is used to materialize
     * the appropriate {@link BigdataValueFactory}.
     */
    private String namespace;

    @Override
    final public String getNamespace() {
        
        return namespace;
        
    }
    
    /**
     * The value factory to be used when creating axioms.
     * 
     * @throws IllegalStateException
     *             unless the ctor variant was used that specifies the database.
     */
    final protected BigdataValueFactory getValueFactory() {
        
        return BigdataValueFactoryImpl.getInstance(namespace);
//        return db.getValueFactory();
        
    }
    
    /**
     * De-serialization constructor.
     */
    protected BaseAxioms() {

//        db = null;
        
    }
    
    /**
     * Ctor variant used by {@link AbstractTripleStore#create()}.
     * <p>
     * Note: When de-serializing a {@link BaseAxioms} object the zero-arg ctor
     * will be used.
     * 
     * @param namespace
     *            The namespace for the {@link AbstractTripleStore} instance.
     */
    protected BaseAxioms(final String namespace) {
     
        if(namespace == null)
            throw new IllegalArgumentException();
        
//        this.db = db;
        this.namespace = namespace;
        
    }
    
    /**
     * Uses {@link #addAxioms(Collection)} to collect the declared axioms and
     * then writes the axioms onto the database specified to the
     * {@link BaseAxioms#BaseAxioms(AbstractTripleStore)} ctor.
     * 
     * @throws IllegalStateException
     *             if that ctor was not used.
     */
    final public void init(final AbstractTripleStore db) {

        // setup [axioms] collection.
        final Set<BigdataStatement> axioms = new LinkedHashSet<BigdataStatement>(
                200);

        // obtain collection of axioms to be used.
        addAxioms(axioms);

        this.axioms = writeAxioms(db, axioms);
        
    }
    
    /**
     * Adds all axioms declared by this class into <i>axioms</i>.
     * <p>
     * Note: Subclasses MUST extend this method to add their axioms into the
     * <i>axioms</i> collection.
     * 
     * @param axioms
     *            A collection into which the axioms will be inserted.
     * 
     * @throws IllegalArgumentException
     *             if the parameter is <code>null</code>.
     */
    protected void addAxioms(final Collection<BigdataStatement> axioms) {

        if (axioms == null)
            throw new IllegalArgumentException();
        
        // NOP.
        
    }

    /**
     * Writes the axioms on the database, resolving {@link BigdataStatement}s to
     * {@link SPO}s.
     * 
     * @return The axioms expressed as {@link SPO}s.
     */
    private Set<SPO> writeAxioms(final AbstractTripleStore db,
            final Collection<BigdataStatement> axioms) {

        if (db == null)
            throw new IllegalArgumentException();

        if (axioms == null)
            throw new IllegalArgumentException();
        
        final int naxioms = axioms.size();

        final LinkedHashSet<SPO> ret = new LinkedHashSet<SPO>(naxioms);
        
        if (naxioms > 0) {

            // Note: min capacity of one handles case with no axioms.
            final int capacity = Math.max(1, naxioms);
            
            final MyStatementBuffer buffer = new MyStatementBuffer(db, capacity);

//            final StatementBuffer<SPO> buffer = new StatementBuffer<SPO>(db, capacity);
//
//			final IChangeLog changeLog = new IChangeLog() {
//
//				@Override
//				public void changeEvent(final IChangeRecord record) {
//
//					final ISPO tmp = record.getStatement();
//					
//					final SPO spo = new SPO(tmp.s(), tmp.p(), tmp.o());
//					
//					ret.add( spo );
//					
//				}
//
//				@Override
//				public void transactionBegin() {
//				}
//
//				@Override
//				public void transactionPrepare() {
//				}
//
//				@Override
//				public void transactionCommited(long commitTime) {
//				}
//
//				@Override
//				public void transactionAborted() {
//				}
//
//				@Override
//				public void close() {
//				}
//			};
//
//			buffer.setChangeLog(changeLog);

            final Iterator<BigdataStatement> itr = axioms.iterator();

            while (itr.hasNext()) {

                final BigdataStatement triple = itr.next();

                assert triple.getStatementType() == StatementEnum.Axiom;

                buffer.add(triple);

            }

            // write on the database.
            buffer.flush();
        
            // SPO[] exposed by our StatementBuffer subclass.
            final SPO[] stmts = ((MyStatementBuffer)buffer).stmts;
            
            for(SPO spo : stmts) {
                
                ret.add(spo);
                
            }
            
        }

        // The axioms as SPO objects.
        return ret;
        
    }
    
//    /**
//     * Create the {@link BTree} to hold the axioms.
//     * 
//     * @param naxioms
//     *            The #of axioms (used to tune the branching factor).
//     * 
//     * @throws IllegalStateException
//     *             if the {@link #btree} exists.
//     */
//    private void createBTree(final int naxioms) {
//        
//        if (btree != null)
//            throw new IllegalStateException();
//                
//        // exact fill of the root leaf.
//        final int branchingFactor = Math.max(Options.MIN_BRANCHING_FACTOR, naxioms );
//        
//        /*
//         * Note: This uses a SimpleMemoryRawStore since we never explicitly
//         * close the BaseAxioms class. Also, all data should be fully
//         * buffered in the leaf of the btree so the btree will never touch
//         * the store after it has been populated.
//         */
//        final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID());
//        
//        metadata.setBranchingFactor(branchingFactor);
//
//        tupleSer = new SPOTupleSerializer(SPOKeyOrder.SPO, false/* sids */);
//        
//        metadata.setTupleSerializer(tupleSer);
//        
//        btree = BTree.createTransient(metadata);
////        btree = BTree.create(new SimpleMemoryRawStore(), metadata);
//
//    }

//    /**
//     * Builds an internal B+Tree that is used to quickly identify axioms based
//     * on {s:p:o} term identifier tuples, and returns the distinct {s:p:o} term
//     * identifier tuples for the declared axioms.
//     * <p>
//     * Note: if the terms for the axioms are already in the lexicon and the
//     * axioms are already in the database then this will not write on the
//     * database, but it will still result in the SPO[] containing the axioms to
//     * be defined in {@link MyStatementBuffer}.
//     * 
//     * @param axioms
//     */
//    private void buildBTree(final Collection<BigdataStatement> axioms) {
//
//        /*
//         * Fill the btree with the axioms in SPO order.
//         * 
//         * Note: This should ALWAYS use the SPO key order even for quads since
//         * we just want to test on the (s,p,o).
//         * 
//         * @todo This would be MUCH faster with a hashmap on the SPOs.
//         * 
//         * @todo There is no need to put the statement type into the in-memory
//         * axioms. they are axioms after all. That is, we could just have the
//         * keys and no values.
//         */
//
//        final int naxioms = axioms.size();
//        
//        createBTree(naxioms/* naxioms */);
//
//        for (BigdataStatement spo : axioms) {
//
//            btree.insert(tupleSer.serializeKey(spo), spo.getStatementType()
//                    .serialize());
//
//        }
//        
//    }
    
//    /**
//     * The initial version. The s, p, and o of each axiom were written out as
//     * <code>long</code> integers.
//     */
//    private static final transient byte VERSION0 = 0;

    /**
     * The serialization format was changed when we introduced the TERMS index
     * (as opposed to the TERM2ID and ID2TERM index). Up to that point, the s,
     * p, and o components were always <code>long</code> termIds assigned by the
     * TERM2ID index. However, the refactor which introduced the TERMS index
     * generalized the {@link IV}s further such that we can no longer rely on
     * the <code>long</code> termId encoding. Therefore, the serialization of
     * the axioms was changed to the length of each {@link SPOKeyOrder#SPO}
     * index key followed by the <code>byte[]</code> comprising that key. This
     * has the effect of using the {@link IV} representation directly within the
     * serialization of the axioms.
     * <p>
     * Note: In the initial version, the s, p, and o of each axiom were written
     * out as <code>long</code> integers. That version is no longer supported.
     * <p>
     * This version also includes the <em>namespace</em> so we can obtain the
     * appropriate {@link BigdataValueFactory} instance without requiring a
     * reference to the {@link AbstractTripleStore}.
     */ 
    private static final transient byte VERSION1 = 1;

    /**
     * The current version.
     */
    private static final transient byte currentVersion = VERSION1;

    @Override
    public void readExternal(final ObjectInput in) throws IOException,
            ClassNotFoundException {

        final byte version = in.readByte();

        switch (version) {
//        case VERSION0:
//            readVersion0(in);
//            break;
        case VERSION1:
            readVersion1(in);
            break;
        default:
            throw new UnsupportedOperationException("Unknown version: "
                    + version);
        }

    }

//    @SuppressWarnings("unchecked")
//    private void readVersion0(final ObjectInput in) throws IOException {
//
//        final long naxioms = LongPacker.unpackLong(in);
//
//        if (naxioms < 0 || naxioms > Integer.MAX_VALUE)
//            throw new IOException();
//
//        createBTree((int) naxioms);
//
//        for (int i = 0; i < naxioms; i++) {
//
//            final IV s = new TermId<BigdataURI>(VTE.URI, in.readLong());
//
//            final IV p = new TermId<BigdataURI>(VTE.URI, in.readLong());
//
//            final IV o = new TermId<BigdataURI>(VTE.URI, in.readLong());
//
//            final SPO spo = new SPO(s, p, o, StatementEnum.Axiom);
//
//            btree.insert(tupleSer.serializeKey(spo), spo.getStatementType()
//                    .serialize());
//
//        }
//    }

    private void readVersion1(final ObjectInput in) throws IOException {

        namespace = in.readUTF();
        
        final int naxioms = LongPacker.unpackInt(in);

//        if (naxioms < 0 || naxioms > Integer.MAX_VALUE)
//            throw new IOException();

        axioms = new LinkedHashSet<SPO>(naxioms);
//        createBTree((int) naxioms);

        for (int i = 0; i < naxioms; i++) {

            final int nbytes = LongPacker.unpackInt(in);

            final byte[] key = new byte[nbytes];

            in.readFully(key);

            final IV[] ivs = IVUtility.decodeAll(key);

            if (ivs.length != 3)
                throw new IOException("Expecting 3 IVs, not: "
                        + Arrays.toString(ivs));

            final IV s = ivs[0];

            final IV p = ivs[1];

            final IV o = ivs[2];

            final SPO spo = new SPO(s, p, o, StatementEnum.Axiom);

//            btree.insert(tupleSer.serializeKey(spo), spo.getStatementType()
//                    .serialize());
            axioms.add(spo);

        }

    }

    public void writeExternal(final ObjectOutput out) throws IOException {

//        if (btree == null)
//            throw new IllegalStateException();

        out.writeByte(currentVersion);

        switch(currentVersion) {
//        case VERSION0: writeVersion0(out); break;
        case VERSION1: writeVersion1(out); break;
        default: throw new AssertionError();
        }

    }

//    private void writeVersion0(final ObjectOutput out) throws IOException {
//
//        final long naxioms = btree.rangeCount();
//
//        LongPacker.packLong(out, naxioms);
//
//        @SuppressWarnings("unchecked")
//        final ITupleIterator<SPO> itr = btree.rangeIterator();
//
//        while (itr.hasNext()) {
//
//            final SPO spo = itr.next().getObject();
//
//            out.writeLong(spo.s().getTermId());
//
//            out.writeLong(spo.p().getTermId());
//
//            out.writeLong(spo.o().getTermId());
//
//        }
//
//    }

    private void writeVersion1(final ObjectOutput out) throws IOException {

        out.writeUTF(namespace);

        LongPacker.packLong(out, axioms.size());

        final IKeyBuilder keyBuilder = new KeyBuilder(24/*initialCapacity*/);
        
        for (ISPO spo : axioms) {

            final byte[] key = SPOKeyOrder.SPO.encodeKey(keyBuilder, spo);

            if (true) {
                
                final IV[] ivs = IVUtility.decodeAll(key);
                
                if (ivs.length != 3)
                    throw new IOException("Expecting 3 IVs, not: "
                            + Arrays.toString(ivs) + " for " + spo);

                final IV s = ivs[0];

                final IV p = ivs[1];

                final IV o = ivs[2];

                final SPO spo2 = new SPO(s, p, o, StatementEnum.Axiom);
                if (!spo.equals(spo2))
                    throw new IOException("Expecting: " + spo + ", not " + spo2);
            }

            LongPacker.packLong(out, key.length);

            out.write(key);

        }
        
    }
    
    final public boolean isAxiom(final IV s, final IV p, final IV o) {

        if (axioms == null)
            throw new IllegalStateException();

        // fast rejection.
        if (s == null || p == null || o == null) {

            return false;
            
        }

        final SPO spo = new SPO(s, p, o, StatementEnum.Axiom);

        if(axioms.contains(spo)) {
            
            return true;
            
        }
        
        return false;
        
    }

    @Override
    public final int size() {
        
        if (axioms == null)
            throw new IllegalStateException();
        
        return axioms.size();
        
    }
    
    @Override
    final public Iterator<SPO> axioms() {
        
        if (axioms == null)
            throw new IllegalStateException();
        
        return Collections.unmodifiableSet(axioms).iterator();
        
    }

    /**
     * Helper class.
     * 
     * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
     */
	private static class MyStatementBuffer extends StatementBuffer<SPO>
			implements StatementBuffer.IWrittenSPOArray {

        /**
         * An array of the axioms in SPO order.
         */
    	private SPO[] stmts;

        /**
         * @param database
         * @param capacity
         */
        public MyStatementBuffer(final AbstractTripleStore database,
                final int capacity) {

            super(database, capacity);

            didWriteCallback = this;
            
        }

        /**
         * Overridden to save off a copy of the axioms in SPO order on
         * {@link #stmts} where we can access them afterwards.
         */
        @Override
        public void didWriteSPOs(final SPO[] stmts, final int numStmts) {
            
            if (this.stmts == null) {

                this.stmts = new SPO[numStmts];

                System.arraycopy(stmts, 0, this.stmts, 0, numStmts);

                Arrays.sort( this.stmts, SPOKeyOrder.SPO.getComparator() );
                
            }
            
//            super.didWriteSPOs(stmts, numStmts);
            
        }
        
    }
    
}

